import { Stats } from "fs";
import Path from "./util/Path"

/**
 * 重写某些模块的方法
 */
let Module = require("module");
if(!require){ global.require = Module.createRequire(__filename);}
import fs from "fs";
import track from "./track";
import path from "path";
let {read,readSync,readFile,readFileSync,
    readdir,readdirSync,createReadStream,
    stat,statSync,lstat,lstatSync,open,openSync,
    exists,existsSync,watch,watchFile} = require("fs")

const extension = [
    ".js",
    ".json"
]

interface _read{
    fd:any;
    option:{
        buffer:Buffer,
        offset:number,
        length:number,
        position:number,  
    };
    callback(err:Error,length:number,data:any):void;
}


interface _stats extends Stats{
    md5:string;
    scope:{
        start:number;
        end:number;
    }
}

export default class insertColumn{
    static packData:Record<string,Buffer> = {};
    static config:Record<string,any> = {};
    static resourcesHandle:Record<number,any> = {} 
    static runPack:string = "";
    static _resolveFilename = Module._resolveFilename;

    static setRunPack(runPack:string){
        this.runPack = runPack;
    }

    static setPackData(packData:Record<string,Buffer>){
        this.packData = packData;
    }
    static setConfig(config:Record<string,any>){
        this.config = config
        Object.keys(config).forEach(e=>{
            this.resourcesHandle[String(config[e].scope.end)] = config[e];
        })
    }

    // 获取绝对路径
    static toObtainAnAbsolutePath(id:string,layer:number=2){
        if(id.startsWith("@/")){
            // 这是打包后导入的文件
            return Path.join(process.cwd(),id.substring(2));
        }else if(id.startsWith(".")){
            return Path.join(track(layer,false),id);
        }else{
            // 这里就属于绝对路径了
            return Path.formatt(id);
        }
    }

    static insertColumn_findPath(){
        let _resolveFilename = Module._resolveFilename;
        Module._resolveFilename = (request: string,paths: any,...a: any)=>{
            request = insertColumn.toObtainAnAbsolutePath(request,3);
            let i = 0;
            if(!path.extname(request)){
                while(true){
                    let fileName = request+extension[i++];
                    if(extension[i-1] === undefined) break;
                    if(this.packData[fileName]) return path.join(fileName);
                }
            }else{
                if(this.packData[request]) return request;
            }
            return _resolveFilename(request,paths,...a);
        }
    }

    static insertColumn_existsSync(){
        let insert = (id: string)=>{
            if(this.packData[id]){return true;}
            return false;
        }
        // @ts-ignore
        fs.exists = (id: string,callback: (arg0: boolean) => any)=>{
            id = insertColumn.toObtainAnAbsolutePath(id,2);
            exists(id,(param: any)=>{
                param?callback(param):callback(insert(id))
            })
        }
        fs.existsSync = (id: any)=>{
            id = insertColumn.toObtainAnAbsolutePath(id,2);
            return existsSync(id)?true:insert(id);
        }
    }

    static insertColumn_readFileSync(){
        let insert = (id: string,option: { encoding: any; })=>{
            let data = this.packData[id];
            if(data){
                if(typeof option === "object"){
                    switch (option.encoding){
                        case "utf8":
                        case "UTF8":
                        case "utf-8":
                        case "UTF-8":{
                            return data.toString();
                        }
                        default:{
                            return data;
                        }
                    } 
                }else{
                    return data.toString();
                }
            }
            return false;
        }

        let pr_readFile = fs.promises.readFile;
        // @ts-ignore
        fs.promises.readFile=async (id: any,option: any)=>{
            id = insertColumn.toObtainAnAbsolutePath(id,2);
            if(existsSync(id)){
                return await pr_readFile(id,option)
            }else{
                let data = insert(id,option);
                if(data) return data;
            }
            console.log([id]);
            throw new Error("File doce not exis")
        }
        // @ts-ignore
        fs.readFile = (id: any,option: any,callback: (arg0: any, arg1: any) => any)=>{
            id = insertColumn.toObtainAnAbsolutePath(id,2);
            let param = this.pars_param(option,callback);
            option = param.option;
            callback = param.callback;
            readFile(id,option,(err: any,data: any)=>{
                if(err){
                    data = insert(id,option);
                    if(data) {
                      return callback && callback(undefined,data);
                    }
                }
                callback && callback(err,data)
            });
        }

        fs.readFileSync = (id: any,option: any,...a: any)=>{
            id = insertColumn.toObtainAnAbsolutePath(id,2);
            if(existsSync(id)){
                return readFileSync(id,option,...a);
            }else{
                let data = insert(id,option);
                if(data)return data;
            }
            console.log([id]);
            throw new Error("file doce not exis")
        }
    }

    static insertColumn_statSync(){
        let insert = (id: string)=>{
            let config = this.getFileConfig(id.replace(process.cwd(),""));
            if(!config) return undefined;
            config.atime = new Date(config.atime);
            config.mtime = new Date(config.mtime);
            config.ctime = new Date(config.ctime);
            config.birthtime = new Date(config.birthtime);
            return {
                ...config,
                isFile(){return true},
                isDirectory(){return false}
            };
        }
        let pr_stat = fs.promises.stat;
        // @ts-ignore
        fs.promises.stat=async (id: any,...a: any)=>{
            id = insertColumn.toObtainAnAbsolutePath(id,2);
            try{
                return await pr_stat(id,...a);
            }catch(err){
                let data = insert(id);
                if(data) return data;
                throw err;
            }
        }
        // @ts-ignore
        fs.stat = (id: any,option: any,callback: (arg0: any, arg1: { isFile(): boolean; isDirectory(): boolean; md5: string; scope: { start: number; end: number; }; isBlockDevice(): boolean; isCharacterDevice(): boolean; isSymbolicLink(): boolean; isFIFO(): boolean; isSocket(): boolean; dev: number; ino: number; mode: number; nlink: number; uid: number; gid: number; rdev: number; size: number; blksize: number; blocks: number; atimeMs: number; mtimeMs: number; ctimeMs: number; birthtimeMs: number; atime: Date; mtime: Date; ctime: Date; birthtime: Date; }) => any)=>{
            id = insertColumn.toObtainAnAbsolutePath(id,2);
            let param = this.pars_param(option,callback);
            option = param.option;
            callback = param.callback;
            stat(id,option,(err: any,data: any)=>{
                if(err){
                    let data = insert(id);
                    if(data) {
                      return callback && callback(undefined,data);
                    }
                }
                callback && callback(err,data);
            })
        }
        // @ts-ignore
        fs.statSync = (id: any,...a: any)=>{
            id = insertColumn.toObtainAnAbsolutePath(id,2);
            try{
                return statSync(id,...a);
            }catch(err){
                let data = insert(id);
                if(data) return data;
                throw err; 
            }
        }
    }

    static insertColumn_createReadStream(){
        fs.createReadStream = (id: string,option:Record<string,any>={})=>{
            // 相对路径需要进行补全
            id = insertColumn.toObtainAnAbsolutePath(id,2);
            if(existsSync(id)){return createReadStream(id,option)};
            // 判断这个文件是否存在
            if(this.packData[id]){
                // 创建读取流
                let config:Record<string,any> = this.getFileConfig(id);
                option.start = config.scope.start+(option.start || 0);
                option.end = config.scope.end+(option.end || 0)-1;
                return createReadStream(this.runPack,option);
            }
            console.log("err:================================")
            console.log([id])
            throw new Error("File does not exist");
        }
    }

    static insertColumn_readdirSync(){
        let insert = (id: string)=>{
            id = insertColumn.toObtainAnAbsolutePath(id,2);
            let filePath = path.resolve(id);
            // 获取地址
            let dir = [];
            Object.keys(this.packData).forEach(e=>{
                if(e.startsWith(filePath)){
                    dir.push(e.replace(filePath,""));
                }
            })
            return dir;
        }
        let pr_readdir = fs.promises.readdir;
        fs.promises.readdir = async (id:string,option:any)=>{
            id = insertColumn.toObtainAnAbsolutePath(id,2);
            if(existsSync(id)){
                return await pr_readdir(id,option)
            }else{
                let dir = insert(id);
                if(dir.length){return dir}
            }
            console.log([id]);
            throw new Error("Folder doce not exis");
        }
        // @ts-ignore
        fs.readdir = (id:string,option:any,callback:any)=>{
            id = insertColumn.toObtainAnAbsolutePath(id,2);
            let param = this.pars_param(option,callback);
            option = param.option;
            callback = param.callback;
            readdir(id,option,(err:Error,data:any)=>{
                if(err){
                    let dir = insert(id);
                    if(dir.length){
                      return callback && callback(undefined,dir);
                    }
                }
                callback && callback(err,data);
            })
        }
        fs.readdirSync = (id:string,option:any)=>{
            id = insertColumn.toObtainAnAbsolutePath(id,2);
            if(existsSync(id)){
                return readdirSync(id,option);
            }else{
                let dir = insert(id);
                if(dir.length) return dir;
            }
            console.log([id]);
            throw new Error("folder doce not exis");
        }
    }

    static insertColumn_lstatSync(){
        let insert = (id:string)=>{
            let filePath = path.resolve(id);
            // 判断是否存在
            for(let e of Object.keys(this.packData)){
                if(e.startsWith(filePath)) {
                    return {
                        isDirectory(){return e!==filePath},
                        isFile(){return e===filePath}
                    }
                }
            }
            return false;
        }
        let pr_lstat = fs.promises.lstat;
        // @ts-ignore
        fs.promises.lstat = async (id:string,option:any)=>{
            id = insertColumn.toObtainAnAbsolutePath(id,2);
            if(existsSync(id)){
                return await pr_lstat(id,option);
            }else{
                let data = insert(id);
                if(data) return data;
            }
            console.log([id]);
            throw new Error("File doce not exis");
        }
        // @ts-ignore
        fs.lstat=(id:string,option:any,callback:any)=>{
            id = insertColumn.toObtainAnAbsolutePath(id,2);
            let param = this.pars_param(option,callback);
            option = param.option;
            callback = param.callback;
            lstat(id,option,(err: any,data: any)=>{
                if(err){
                    let data = insert(id);
                    if(data) return callback && callback(undefined,data)
                }
                callback && callback(err,data)
            })
        }
        // @ts-ignore
        fs.lstatSync = (id: string,...a: any)=>{
            id = insertColumn.toObtainAnAbsolutePath(id,2);
            try{
                return lstatSync(id,...a);
            }catch(err){
                let data = insert(id);
                if(data) return data;
                throw err;
            }
        }
    }

    static insertColumn_watch(){
        fs.watch = (dirPath:string,...a: any)=>{
            dirPath = insertColumn.toObtainAnAbsolutePath(dirPath,2);
            try{
                return watch(dirPath,...a)
            }catch(err){
                if(Object.keys(this.packData).some(e=>e.startsWith(dirPath))) {
                    fs.mkdirSync("./tem");
                    let a = watch("./tem");
                    fs.unlinkSync("./tem");
                    return a;
                }
                throw err;
            }
        }
    }

    static insertColumn_watchFile(){
        fs.watchFile = (filePath: string,...a:any)=>{
            filePath = insertColumn.toObtainAnAbsolutePath(filePath,2);
            try{
                return watchFile(filePath,...a)
            }catch(err){
                if(Object.keys(this.packData).some(e=>e===filePath)) return;
                throw err;
            }
        }
    }

    static insertColumn_open(){
        // @ts-ignore
        fs.open = (id: any,flags: any,mode: any,callback: (arg0: any, arg1: any) => void)=>{
            id = insertColumn.toObtainAnAbsolutePath(id,2);
            open(id,flags,mode,(err: any,fd: any)=>{
                if(err){
                    let data = fs.statSync(id);
                    data && callback(undefined,data);
                    return;
                }
                callback(err,fd);
            });
        }

        fs.openSync = function (id: any,flags: any,mode: any){
            id = insertColumn.toObtainAnAbsolutePath(id,2);
            try{
                return openSync(id,flags,mode);
            }catch(err){
                let data = fs.statSync(id);
                // if(data) return data.scope.end*-1;
                if(data) return data;

                throw err;
            }
        }
        
    }

    static pars_read(fd:any,buffer:Buffer,offset:number|any,length:number,position:number,callback:any):_read{
        if(buffer instanceof Buffer){
            return {fd,option:{
                buffer,
                offset:offset || 0,
                length,
                position:position || 0,
            },callback}
        }else if(typeof buffer === "function"){
            return {fd,option:undefined,callback:buffer}
        }else{
            return {fd,option:buffer,callback:offset}
        }
    }
    
    static insertColumn_read(){
        let insert = (pars:_read)=>{
            if(pars.option){
                let stat:any = pars.fd; // this.resourcesHandle[pars.fd];
                if(typeof stat === "object"){
                    let data = this.packData[path.join(process.cwd(),stat.filePath)];
                    // 获取打包文件中的数据
                    for(let i = pars.option.position;i<pars.option.length+pars.option.position;i++){
                        pars.option.buffer[pars.option.offset+i] = data[i];
                    }
                    return pars.option.buffer;
                }
            }else{
                let data = this.packData[path.join(process.cwd(),stat.filePath)];
                return Buffer.from(data);
            }
            return false;
        }

        // @ts-ignore
        fs.read = (fd:any,buffer:Buffer,offset:number,length:number,position:number,callback:any)=>{
            let pars = this.pars_read(fd,buffer,offset,length,position,callback)
            let data = insert(pars);
            if(data) return pars.callback && pars.callback(undefined,data.length,data);
            read(fd,buffer,offset,length,position,callback)
        }
        
        // @ts-ignore
        fs.readSync = (fd: any,buffer: Buffer,offset: any,length: number,position: number)=>{
            let pars = this.pars_read(fd,buffer,offset,length,position,undefined)
            let data = insert(pars);
            if(data) return data
            return readSync(fd,buffer,offset,length,position);
        }
    }

    static getFileConfig(id: string):_stats{
        return this.config[id.replace(process.cwd(),"")]
    }
    /**
     * 解析参数
     */
    static pars_param(option: any,callback: any){
        if(typeof option === "object"){
            return {option,callback}
        }else if(typeof option === "function"){
            return {option:{},callback:option}
        }else{
            return {option:{},callback}
        }
    }
}